package optimizer;

import java.util.ArrayList;
import simulator.*;

/**
 * This class generates the most straight forward build order to get a maximum time the build should take.
 * This strategy favours simple build orders (eg 6 Marines).
 */
public class BasicOptimizerStrategy
{
	GameState targetState;
	GameState initialState;
	Simulator simulator;
	
	public BasicOptimizerStrategy(GameState targetState, GameState initialState, Simulator simulator) {
		this.targetState = targetState;
		this.initialState = initialState;
		this.simulator = simulator;
	}
	
	/**
	 * This function populates the returnedStrategy array with the simplest strategy and then returns the in-game
	 * time taken to run this build order
	 * Assumes that we will generate the materials without any interaction. Needs adjustment for gas
	 * @param returnedStrategy
	 * @param targetState is the target gameState we want to reach
	 * @return Game time to reach target
	 */
	public Strategy getTimeAndStrategyForSimplestBuildOrder() {
		// Get dependencies of targetState in correct order
		ArrayList<ArrayList<GameAsset>> buildOrders = new ArrayList<ArrayList<GameAsset>>(); 
		
		GameState target = new GameState(this.targetState);
		
		for(GameAsset asset : GameAsset.values()) {
			if(target.getNumberOfAsset(asset) > 0) {
				buildOrders.add( getBuildOrderForAsset(asset, target.getNumberOfAsset(asset)) );
			}
		}
		
		// Generate simplest build order
		ArrayList<GameAsset> buildOrderList = mergeBuildOrderDependencies(buildOrders);
		
		// If we require gas, we need to build at least one refinery
		// This simple strategy just builds the refinery first to keep things simple.
		boolean isGasRequired = OptimizerUtilities.calculateTotalVespeneRequired(buildOrderList.toArray(new GameAsset[buildOrderList.size()])) > 0;
		
		if(isGasRequired) {
			buildOrderList.add(0, GameAsset.REFINERY);
		}
		
		int additionalSupplyDepots = OptimizerUtilities.calculateAdditionalSupplyDepotsRequired(buildOrderList.toArray(new GameAsset[buildOrderList.size()]));
		// loop to add the required supply depots to the buildOrderList, again we add this at the beginning
		for(int i=0; i<additionalSupplyDepots; i++) {
			buildOrderList.add(0, GameAsset.SUPPLY_DEPOT);
		}
		
		GameAsset[] buildOrder = buildOrderList.toArray(new GameAsset[buildOrderList.size()]);
		
		// Simulate this build order
		int currentStep = 0;
		GameState simulatedState = new GameState(initialState);
		
		// We allocate workers very simply in this estimate
		if(isGasRequired) {
			simulatedState.setWorkersTask(5, 1);
		}
		else {
			simulatedState.setWorkersTask(6, 0);
		}
		
		// We store strategy as we simulate it
		Strategy strategy = new Strategy();
		
		while(!simulatedState.reachedTarget(targetState)) {
			// Get all available choices for state
			ArrayList<GameChoice> choices = simulator.getAllAvailableChoices(simulatedState);
			
			GameChoice chosenChoice = null;
			
			// If we haven't already executed every build order
			if(currentStep < buildOrder.length)	{
				// for every available choice
				for(GameChoice choice : choices) {
					if(choice.getTask() == GameMove.BUILD) {
						// if one of the choices matches the next buildOrder instruction
						if(choice.getGameAsset() == buildOrder[currentStep]) {
							// choose it and increment the next item to be built
							currentStep++;
							chosenChoice = choice;
							break;
						}
					}
					else if(choice.getTask() == GameMove.WAIT) {
						// else if we haven't found a better choice yet, wait
						if(chosenChoice == null) {
							chosenChoice = choice;
						}
					}
				}
				if(chosenChoice == null) {
					// Something has gone wrong and we have ended up with no choices. This should not happen
					throw new Error("BuildOrderOptimizerStrategy has no choices left");
				}
				
				simulatedState = simulator.choose(simulatedState, chosenChoice);
				strategy.addMove(chosenChoice, simulatedState.getTime());
			}
			
			// Whether or not we've executed all build orders, we still want to increase time etc
			simulator.tickState(simulatedState);
		}
		// Tell the strategy object that we've reached the target at this time.
		strategy.setStrategyCompleted(simulatedState.getTime());
		return strategy;
	}
	
	/**
	 * This function generates a theoretical build order for the supplied asset (including the number required).
	 * This function does no optimization at all and merely just returns the simplest build order
	 * @param asset
	 * @param numberOfAsset
	 * @return the list which states in which order to build assets
	 */
	public ArrayList<GameAsset> getBuildOrderForAsset(GameAsset asset, int numberOfAsset) {
		ArrayList<GameAsset> assets = OptimizerUtilities.getDependentAssets(new ArrayList<GameAsset>(), asset);
		
		for(int i=0; i<numberOfAsset-1; i++) {
			assets.add(asset);
		}
		
		return assets;
	}
	
	/**
	 * This function merges build orders in the simplest way.
	 *	Ensures that we don't have 2 barracks just because we need to build 1 hellion and 1 marine, etc.
	 */
	private ArrayList<GameAsset> mergeBuildOrderDependencies(ArrayList<ArrayList<GameAsset>> buildOrders)
	{
		ArrayList<GameAsset> assets = new ArrayList<GameAsset>();
		
		for(ArrayList<GameAsset> outerList : buildOrders) {
			for(GameAsset asset : outerList) {
				// if the asset is listed in the Target State, we must want the same number as listed 
				// in targetState
				
				if(targetState.getNumberOfAsset(asset) > 0) {
					assets.add(asset);
				}
				else {
					if(!assets.contains(asset)) {
						assets.add(asset);
					}
				}
			}
		}
		return assets;
	}
}
